﻿using System;
using System.Collections;
using System.IO;
using System.Xml;
namespace Moonlite.Services.CodeCompletion
{
    public class AndromedaParser
    {
        private AndromedaSourceType GetSourceTypeFromString(string type)
        {
            switch (type.ToLower())
            {
                case "file":
                    return AndromedaSourceType.File;
                default:
                    return AndromedaSourceType.File;
            }
        }

        private void FindSuperClasses(AndromedaSourceCollection sources)
        {
            foreach (AndromedaSource source in sources)
            {
                Hashtable nameTable = new Hashtable();
                AndromedaObjectClassCollection classes = new AndromedaObjectClassCollection();
                foreach (AndromedaObject @object in source.GetAllChildren())
                {
                    if (@object is AndromedaObjectClass)
                    {
                        AndromedaObjectClass @class = @object as AndromedaObjectClass;
                        nameTable.Add(@class.Name, @class);
                        classes.Add(@class);
                    }
                }

                foreach (AndromedaObjectClass @class in classes)
                {
                    if (!string.IsNullOrEmpty(@class.SuperClassTempName) && !@class.ValidSuperClass)
                    {
                        if (nameTable.ContainsKey(@class.SuperClassTempName))
                        {
                            @class.Extends = (AndromedaObjectClass)nameTable[@class.SuperClassTempName];
                            @class.SuperClassTempName = null;
                            @class.ValidSuperClass = true;
                        }
                    }
                }
            }
        }

        private void FindSuperClasses(AndromedaStructure structure)
        {
            FindSuperClasses(structure.Sources);
        }

        private void RunParseMethodsFromNode(XmlNode node, AndromedaObject parent)
        {
            foreach (XmlNode childNode in node)
            {
                switch (childNode.Name.ToLower())
                {
                    case "accessor":
                        ParseProperty(childNode, parent);
                        break;
                    case "class":
                        ParseClass(childNode, parent);
                        break;
                    case "description":
                        ParseDescription(childNode, parent);
                        break;
                    case "enrich":
                        ParseEnrichment(childNode, parent);
                        break;
                    case "globalvariable":
                    case "field":
                        ParseField(childNode, parent);
                        break;
                    case "function":
                    case "method":
                        ParseMethod(childNode, parent);
                        break;
                    case "struct":
                        ParseStruct(childNode, parent);
                        break;
                }
            }
        }

        public AndromedaStructure Parse(string filePath)
        {
            return Parse(File.Open(filePath, FileMode.Open));
        }

        public AndromedaStructure Parse(Stream stream)
        {
            AndromedaStructure structure = new AndromedaStructure();
            XmlDocument document = new XmlDocument();
            document.Load(stream);
            foreach (XmlNode structureNode in document.ChildNodes)
            {
                if (structureNode.Name.ToLower() == "andromedastructure")
                {
                    foreach (XmlNode sourceNode in structureNode.ChildNodes)
                    {
                        if (sourceNode.Name.ToLower() == "source")
                        {
                            string name = "";
                            AndromedaSourceType type = AndromedaSourceType.File;
                            int inclusionType = 0;
                            string path = "";
                            foreach (XmlAttribute attribute in sourceNode.Attributes)
                            {
                                switch (attribute.Name.ToLower())
                                {
                                    case "name":
                                        name = attribute.Value;
                                        break;
                                    case "type":
                                        type = GetSourceTypeFromString(attribute.Value);
                                        break;
                                    case "inclusiontype":
                                        inclusionType = int.Parse(attribute.Value);
                                        break;
                                    case "path":
                                        path = attribute.Value.Replace(Uri.UriSchemeFile + "://", "");
                                        break;
                                }
                            }

                            if (name != "")
                            {
                                AndromedaSource source = new AndromedaSource(name, type, inclusionType, path);
                                source.Parent = structure;
                                source = ProcessDscFormatTags(source);
                                ParseSourceContents(sourceNode, source);
                                structure.Sources.Add(source);
                            }
                        }
                    }
                }
            }

            FindSuperClasses(structure);
            return structure;
        }


        private AndromedaSource ProcessDscFormatTags(AndromedaSource source)
        {
            string fileContents = "";
            using (StreamReader reader = new StreamReader(source.Path))
            {
                fileContents = reader.ReadToEnd();
            }

            string[] lines = fileContents.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
            if (lines.Length > 0)
            {
                string tagLine = lines[0];
                if (tagLine.StartsWith("//DSCFormat:"))
                {
                    string[] tags = tagLine.Replace("//DSCFormat:", "").Split(';');
                    foreach (string tag in tags)
                    {
                        string[] equalSplit = tag.Split('=');
                        if (equalSplit.Length == 2)
                        {
                            string key = equalSplit[0];
                            string value = equalSplit[1];
                            switch (key)
                            {
                                case "Path":
                                    source.Path = value;
                                    break;
                                case "Project":
                                    break;
                            }
                        }
                    }
                    source.TagLength = tagLine.Length + Environment.NewLine.Length;
                }
            }

            return source;
        }

        private void ParseSourceContents(XmlNode sourceNode, AndromedaSource source)
        {
            RunParseMethodsFromNode(sourceNode, source);
        }

        private void AddObjectToSpecificObject(AndromedaObject @object, AndromedaObject specificObject)
        {
            if (specificObject is AndromedaObjectWithSpecificChildren)
            {
                AndromedaObjectWithSpecificChildren realSpecificObject = (AndromedaObjectWithSpecificChildren)specificObject;
                if (@object is AndromedaObjectClass)
                {
                    realSpecificObject.Classes.Add((AndromedaObjectClass)@object);
                }
                else if (@object is AndromedaObjectEnrichment)
                {
                    realSpecificObject.Enrichments.Add((AndromedaObjectEnrichment)@object);
                }
                else if (@object is AndromedaObjectField)
                {
                    realSpecificObject.Fields.Add((AndromedaObjectField)@object);
                }
                else if (@object is AndromedaObjectMethod)
                {
                    realSpecificObject.Methods.Add((AndromedaObjectMethod)@object);
                }
                else if (@object is AndromedaObjectProperty)
                {
                    realSpecificObject.Properties.Add((AndromedaObjectProperty)@object);
                }
                else if (@object is AndromedaObjectStruct)
                {
                    realSpecificObject.Structs.Add((AndromedaObjectStruct)@object);
                }
            }
        }

        private void ParseObject(XmlNode node, AndromedaObject @object)
        {
            foreach (XmlAttribute attribute in node.Attributes)
            {
                switch (attribute.Name.ToLower())
                {
                    case "offset":
                        @object.Offset = int.Parse(attribute.Value);
                        break;
                    case "length":
                        @object.Length = int.Parse(attribute.Value);
                        break;
                }
            }
        }

        private AndromedaObjectClass ParseClass(XmlNode node, AndromedaObject parent)
        {
            AndromedaObjectClass @class = new AndromedaObjectClass(parent);
            ParseObject(node, @class);
            foreach (XmlAttribute attribute in node.Attributes)
            {
                switch (attribute.Name.ToLower())
                {
                    case "name":
                        @class.Name = attribute.Value;
                        break;
                    case "visibility":
                        @class.Visibility = attribute.Value;
                        break;
                    case "static":
                        @class.Static = bool.Parse(attribute.Value);
                        break;
                    case "final":
                        @class.Final = bool.Parse(attribute.Value);
                        break;
                    case "extends":
                        @class.SuperClassTempName = attribute.Value;
                        @class.ValidSuperClass = false;
                        break;
                }
            }

            AddObjectToSpecificObject(@class, parent);
            RunParseMethodsFromNode(node, @class);
            parent.Children.Add(@class);
            if (parent is AndromedaSource) parent.Parent.Children.Add(@class);
            return @class;
        }

        private AndromedaObjectDescription ParseDescription(XmlNode node, AndromedaObject parent)
        {
            AndromedaObjectDescription description = new AndromedaObjectDescription(parent);
            ParseObject(node, description);
            AddObjectToSpecificObject(description, parent);
            parent.Children.Add(description);
            return description;
        }

        private AndromedaObjectEnrichment ParseEnrichment(XmlNode node, AndromedaObject parent)
        {
            AndromedaObjectEnrichment enrichment = new AndromedaObjectEnrichment(parent);
            ParseObject(node, enrichment);
            foreach (XmlAttribute attribute in node.Attributes)
            {
                switch (attribute.Name.ToLower())
                {
                    case "type":
                        enrichment.Name = attribute.Value;
                        break;
                    case "visibility":
                        enrichment.Visibility = attribute.Value;
                        break;
                }
            }

            AddObjectToSpecificObject(enrichment, parent);
            RunParseMethodsFromNode(node, enrichment);
            parent.Children.Add(enrichment);
            if (parent is AndromedaSource) parent.Parent.Children.Add(enrichment);
            return enrichment;
        }

        private AndromedaObjectField ParseField(XmlNode node, AndromedaObject parent)
        {
            AndromedaObjectField field = new AndromedaObjectField(parent);
            ParseObject(node, field);
            foreach (XmlAttribute attribute in node.Attributes)
            {
                switch (attribute.Name.ToLower())
                {
                    case "name":
                        field.Name = attribute.Value;
                        break;
                    case "type":
                        field.Type = attribute.Value;
                        break;
                    case "visibility":
                        field.Visibility = attribute.Value;
                        break;
                    case "value":
                        field.Value = attribute.Value;
                        break;
                    case "const":
                        field.Const = bool.Parse(attribute.Value);
                        break;
                    case "static":
                        field.Static = bool.Parse(attribute.Value);
                        break;
                }
            }

            AddObjectToSpecificObject(field, parent);
            RunParseMethodsFromNode(node, field);
            parent.Children.Add(field);
            if (parent is AndromedaSource) parent.Parent.Children.Add(field);
            return field;
        }

        private AndromedaObjectMethod ParseMethod(XmlNode node, AndromedaObject parent)
        {
            AndromedaObjectMethod method = new AndromedaObjectMethod(parent);
            ParseObject(node, method);
            foreach (XmlAttribute attribute in node.Attributes)
            {
                switch (attribute.Name.ToLower())
                {
                    case "name":
                        method.Name = attribute.Value;
                        break;
                    case "returntype":
                        method.Type = attribute.Value;
                        break;
                    case "visibility":
                        method.Visibility = attribute.Value;
                        break;
                    case "signature":
                        method.Signature = attribute.Value;
                        break;
                    case "final":
                        method.Final = bool.Parse(attribute.Value);
                        break;
                    case "abstract":
                        method.Abstract = bool.Parse(attribute.Value);
                        break;
                    case "static":
                        method.Static = bool.Parse(attribute.Value);
                        break;
                    case "native":
                        method.Native = bool.Parse(attribute.Value);
                        break;
                    case "override":
                        method.Override = bool.Parse(attribute.Value);
                        break;
                }
            }

            foreach (XmlNode childNode in node.ChildNodes)
            {
                if (childNode.Name.ToLower() == "param" || childNode.Name.ToLower() == "local")
                {
                    AndromedaObjectField field = new AndromedaObjectField(method);
                    foreach (XmlAttribute attribute in childNode.Attributes)
                    {
                        switch (attribute.Name.ToLower())
                        {
                            case "name":
                                field.Name = attribute.Value;
                                break;
                            case "type":
                                field.Type = attribute.Value;
                                break;
                            case "visibility":
                                field.Visibility = attribute.Value;
                                break;
                        }
                    }
                    if (childNode.Name.ToLower() == "param")
                    {
                        method.Parameters.Add(field);
                    }
                    else
                    {
                        method.Children.Add(field);
                    }
                }
            }

            AddObjectToSpecificObject(method, parent);
            RunParseMethodsFromNode(node, method);
            parent.Children.Add(method);
            if (parent is AndromedaSource) parent.Parent.Children.Add(method);
            return method;
        }

        private AndromedaObjectProperty ParseProperty(XmlNode node, AndromedaObject parent)
        {
            AndromedaObjectProperty property = new AndromedaObjectProperty(parent);
            ParseObject(node, property);
            foreach (XmlAttribute attribute in node.Attributes)
            {
                switch (attribute.Name.ToLower())
                {
                    case "name":
                        property.Name = attribute.Value;
                        break;
                    case "type":
                        property.Type = attribute.Value;
                        break;
                    case "visibility":
                        property.Visibility = attribute.Value;
                        break;
                    case "static":
                        property.Static = bool.Parse(attribute.Value);
                        break;
                }
            }

            AddObjectToSpecificObject(property, parent);
            RunParseMethodsFromNode(node, property);
            parent.Children.Add(property);
            if (parent is AndromedaSource) parent.Parent.Children.Add(property);
            return property;
        }

        private AndromedaObjectStruct ParseStruct(XmlNode node, AndromedaObject parent)
        {
            AndromedaObjectStruct @struct = new AndromedaObjectStruct(parent);
            ParseObject(node, @struct);
            foreach (XmlAttribute attribute in node.Attributes)
            {
                switch (attribute.Name.ToLower())
                {
                    case "name":
                        @struct.Name = attribute.Value;
                        break;
                    case "visibility":
                        @struct.Visibility = attribute.Value;
                        break;
                }
            }

            AddObjectToSpecificObject(@struct, parent);
            RunParseMethodsFromNode(node, @struct);
            parent.Children.Add(@struct);
            if (parent is AndromedaSource) parent.Parent.Children.Add(@struct);
            return @struct;
        }
    }
}
